package be.android.forap.crypto;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.jce.spec.ElGamalParameterSpec;

import be.android.forap.Constants;
import be.android.forap.network.DropboxUtil;

import android.util.Log;

public class KeyUtil {
	
	private static final String TAG = "KeyUtil";

	public final static int RSA_ENCRYPT = 0;
	public final static int RSA_SIGN = 1;
	public final static int ELGAMAL_ENCRYPT = 2;
	public final static int DSA_SIGN = 3;
	
	
	public static byte[] generatePBEKeyWithGivenSalt(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
	    if(salt.length > 16)
	    	throw new InvalidKeySpecException();
	    //http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption
	    /* Derive the key, given password and salt. */
	    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
	    KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
	    SecretKey tmp = factory.generateSecret(spec);
	    Log.e(TAG,tmp.getEncoded().length + " "+ new String(tmp.getEncoded()));
	    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
	    Log.e(TAG,secret.getEncoded().length + " "+ new String(secret.getEncoded()));
	    return secret.getEncoded();
	}
	
	
	public static byte[] generatePBEKey(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
	    byte salt[] = new byte[16];
	    SecureRandom saltGen = SecureRandom.getInstance("SHA1PRNG");
	    saltGen.nextBytes(salt);
	    DropboxUtil.uploadFile(salt, Constants.SALT_FILE);
	    Log.e(TAG, "salt "+ salt);
	    //http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption
	    /* Derive the key, given password and salt. */
	    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
	    KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
	    SecretKey tmp = factory.generateSecret(spec);
	    Log.e(TAG,tmp.getEncoded().length + " "+ new String(tmp.getEncoded()));
	    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
	    Log.e(TAG,secret.getEncoded().length + " "+ new String(secret.getEncoded()));
	    return secret.getEncoded();
	}

	
	private static KeyPair generateRSAEncryptionKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException{
		KeyPairGenerator rsaKpg = KeyPairGenerator.getInstance("RSA", "SC");
		rsaKpg.initialize(keySize);
		return rsaKpg.generateKeyPair();		
	}
	
	private static KeyPair generateRSASignKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException{
		KeyPairGenerator rsaKpg = KeyPairGenerator.getInstance("RSA", "SC");
		rsaKpg.initialize(keySize);
		return rsaKpg.generateKeyPair();		
	}
	
	private static KeyPair generateElGamalKey() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException{
		KeyPairGenerator    elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "SC");
		// this is quicker because we are using pregenerated parameters.		
		BigInteger          g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
		BigInteger          p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
		ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g);
		elgKpg.initialize(elParams);
		return elgKpg.generateKeyPair();		
	}
	
	private static KeyPair generateDSASignKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException{
		KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "SC");
		dsaKpg.initialize(keySize);
		return dsaKpg.generateKeyPair();		
	}
	
	public static void generateRSAKeypair(OutputStream secretOut, OutputStream publicOut, String identity, char[] passPhrase, boolean armor) 
			throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, PGPException, IOException{
		Security.addProvider(new BouncyCastleProvider());
		
		int keySize = 2048;
		KeyPair rsaEncKp = generateRSAEncryptionKey(keySize);
		KeyPair rsaSignKp = generateRSASignKey(keySize);
		PGPKeyPair RSAsignKeyPair = new PGPKeyPair(PGPPublicKey.RSA_SIGN, rsaSignKp, new Date());
		PGPKeyPair RSAencryptKeyPair = new PGPKeyPair(PGPPublicKey.RSA_ENCRYPT, rsaEncKp, new Date());

		PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, RSAsignKeyPair, identity, PGPEncryptedData.AES_256, passPhrase, true, null, null, new SecureRandom(), "SC");

		keyRingGen.addSubKey(RSAencryptKeyPair);
		if (armor)
			secretOut = new ArmoredOutputStream(secretOut);
		keyRingGen.generateSecretKeyRing().encode(secretOut);
		secretOut.close();

		if (armor)
			publicOut = new ArmoredOutputStream(publicOut);
		keyRingGen.generatePublicKeyRing().encode(publicOut);
		publicOut.close();
	}

	public static void generateElGamalDSAKeyPair(OutputStream secretOut, OutputStream publicOut, String identity, char[] passPhrase, boolean armor)
			throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, PGPException, IOException{
		Security.addProvider(new BouncyCastleProvider());		
		int keySize = 2048;
		KeyPair dsaKp = generateDSASignKey(keySize);		
		KeyPair elgKp = generateElGamalKey();

		PGPKeyPair dsaKeyPair = new PGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
		PGPKeyPair elgKeyPair = new PGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());

		PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair, identity, PGPEncryptedData.AES_256, passPhrase, true, null, null, new SecureRandom(), "SC");

		keyRingGen.addSubKey(elgKeyPair);
		if (armor)
			secretOut = new ArmoredOutputStream(secretOut);
		keyRingGen.generateSecretKeyRing().encode(secretOut);
		secretOut.close();

		if (armor)
			publicOut = new ArmoredOutputStream(publicOut);
		keyRingGen.generatePublicKeyRing().encode(publicOut);
		publicOut.close();
	}

	public static  ArrayList<PGPPublicKey> loadPublicKeys(InputStream input) throws IOException, PGPException{
		ArrayList<PGPPublicKey> publicKeysList = new ArrayList<PGPPublicKey>();
		
		PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input));
		
		Iterator keyRingIter = pgpPub.getKeyRings();
		while (keyRingIter.hasNext()) {
			PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
			Iterator keyIter = keyRing.getPublicKeys();
			while (keyIter.hasNext()) {
				PGPPublicKey key = (PGPPublicKey)keyIter.next();
				publicKeysList.add(key);
			}
		}
		return publicKeysList;
	}
	
	public static int getNbOfPublicKeys(InputStream input) throws IOException, PGPException{
		ArrayList<PGPPublicKey> publicKeysList = loadPublicKeys(input);
		return publicKeysList.size();
	}
	
	public static PGPPublicKey[] getPublicKeyList(InputStream input) throws IOException, PGPException{
		ArrayList<PGPPublicKey> publicKeysList = loadPublicKeys(input);
		PGPPublicKey[] out = new PGPPublicKey[publicKeysList.size()];
		publicKeysList.toArray(out);
		return out;
	}
	
	

	public static String readPublicKeyInfo(InputStream input) throws IOException, PGPException {
		PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input));
		String out = "Public key reading \n"; 
		//
		// we just loop through the collection till we find a key suitable for encryption, in the real
		// world you would probably want to be a bit smarter about this.
		//

		Iterator keyRingIter = pgpPub.getKeyRings();
		while (keyRingIter.hasNext())
		{
			PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
			out = out + "KeyRing: "+keyRing.toString() +"\n";
			Iterator keyIter = keyRing.getPublicKeys();
			while (keyIter.hasNext())
			{
				PGPPublicKey key = (PGPPublicKey)keyIter.next();
				out = out + "PGPPublicKey: \n Is master "+key.isMasterKey()+" \n Is encryption "+key.isEncryptionKey()+" \n Alg "+CryptoUtil.getPublicKeyAlgorithm(key.getAlgorithm()) +"\n"+key.getKeyID();
				/*if (key.isEncryptionKey())
				{
					return key;
				}*/
			}
		}
		return out;
	}
	
	
}
